use crate::qbus::QBus;
use crate::qmessage::{QMessage, QMessageInfo};
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use embed_std::{errorln, infoln};

pub const TOPIC_ALL: &str = "";

pub struct QConsumer {
    cursor_id: usize,
    topics: Vec<String>,
    bus: QBus,
    filter: Box<dyn QMessageFilter>,
}

impl QConsumer {
    pub fn new(bus: QBus) -> Self {
        Self::new_with_filter(bus, Box::new(PrefixFilter {}))
    }

    pub fn new_with_filter(bus: QBus, filter: Box<dyn QMessageFilter>) -> Self {
        let cursor_id = bus.alloc_cursor();
        if cursor_id.is_none() {
            errorln!("bus cursor full!");
            panic!("bus cursor full!");
        }
        Self {
            cursor_id: cursor_id.unwrap(),
            topics: Vec::new(),
            bus,
            filter,
        }
    }

    pub fn pos(&self) -> u64 {
        self.bus.get_cursor_pos(self.cursor_id)
    }

    pub fn cursor(&self) -> usize {
        self.cursor_id
    }

    pub fn reset(&mut self) {
        self.bus.reset_cursor(self.cursor_id);
    }

    pub fn subscribe(&mut self, topic: &str) -> &mut Self {
        if self.topics.contains(&topic.to_owned()) {
            return self;
        }
        self.topics.push(topic.to_owned());
        self
    }

    pub fn unsubscribe(&mut self, topic: &str) -> &mut Self {
        for i in 0..self.topics.len() {
            if self.topics[i] == topic {
                self.topics.remove(i);
                break;
            }
        }
        self
    }

    pub fn read(&self) -> Option<QMessage> {
        self.bus.read(self)
    }

    #[deprecated]
    pub fn read_to_buff(&self, data: &mut [u8]) -> Option<(usize, QMessageInfo)> {
        self.bus.read_to_buff(self, data)
    }

    pub fn read_map<F>(&self, f: F) -> bool
    where
        F: FnOnce(&QMessage),
    {
        self.bus.read_map(self, f)
    }

    pub fn match_filter(&self, msg: &QMessage) -> bool {
        //@TODO pattern
        for f in self.topics.iter() {
            if self.filter.filter(f.as_str(), msg) {
                return true;
            }
        }
        false
    }

    pub fn send_resp(&self, seq: u32, data: Vec<u8>) {
        self.bus.push_resp(seq, data);
    }
}

impl Drop for QConsumer {
    fn drop(&mut self) {
        infoln!("drop QConsumer");
        self.bus.reclaim_cursor(self.cursor_id);
    }
}

pub trait QMessageFilter: Send + Sync {
    fn filter(&self, sub_topic: &str, msg: &QMessage) -> bool;
}

pub struct PrefixFilter {}

impl QMessageFilter for PrefixFilter {
    fn filter(&self, sub_topic: &str, msg: &QMessage) -> bool {
        sub_topic == TOPIC_ALL || sub_topic.find(msg.topic.as_str()) == Some(0)
    }
}
